/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// gl_vidnt.c -- primary video interface, calls into vid_d3d.c, vid_menu.c and vid_win32.c for specific functionality

#include "quakedef.h"

#pragma comment (lib, "d3d10.lib")
#pragma comment (lib, "d3dx10.lib")
#pragma comment (lib, "dxgi.lib")
#pragma comment (lib, "winmm.lib")
#pragma comment (lib, "Comctl32.lib")

// global video state
viddef_t	vid;

unsigned d_8to24table_rgba[256];
unsigned d_8to24table_luma[256];


// video cvars
cvar_t *vid_mode;
cvar_t *vid_fullscreen;
cvar_t *vid_width;
cvar_t *vid_height;
cvar_t *vid_vsync;

#define MAX_VID_HANDLERS	128


static CD3DHandler *vid_handlers[MAX_VID_HANDLERS];
int num_vid_handlers = 0;


CD3DHandler::CD3DHandler (void)
{
	// this can't throw a regular error as it happens during static initialization time
	// also fucks up with std::vector so just add it to a big static array.  sigh.
	if (num_vid_handlers < MAX_VID_HANDLERS)
	{
		vid_handlers[num_vid_handlers] = this;
		num_vid_handlers++;
	}
}


void VID_RunHandlers (vidhandlertype_t type)
{
	if (!d3d_Device) return;

	for (int i = 0; i < num_vid_handlers; i++)
	{
		switch (type)
		{
		case handler_init:
			vid_handlers[i]->OnCreateDevice ();
			break;

		case handler_shutdown:
			vid_handlers[i]->OnReleaseDevice ();
			break;

		case handler_resetdevice:
			vid_handlers[i]->OnResetDevice ();
			break;

		case handler_lostdevice:
			vid_handlers[i]->OnLostDevice ();
			break;

		case handler_modechange:
			vid_handlers[i]->OnModeChange ();
			break;

		case hander_perframe:
			vid_handlers[i]->OnBeginFrame ();
			break;
		}
	}
}


void VID_ModeChanged (void)
{
	static BOOL recursive = FALSE;

	if (!recursive)
	{
		// prevent this from being called recursively
		recursive = TRUE;

		// only trigger this if the number of modes have been fully enumerated
		if (vid.NumModes > 0)
		{
			RECT workarea;

			// check vid_mode value to prevent it from going out-of-range
			if (vid_mode->integer < 0) Cvar_SetValue (vid_mode, 0);
			if (vid_mode->integer >= vid.NumModes) Cvar_SetValue (vid_mode, vid.NumModes - 1);

			// get the working area for bounding vid_width and vid_height
			SystemParametersInfo (SPI_GETWORKAREA, 0, &workarea, 0);

			// and bound it
			if (vid_width->integer > RectWidth (&workarea)) Cvar_SetValue (vid_width, RectWidth (&workarea));
			if (vid_height->integer > RectHeight (&workarea)) Cvar_SetValue (vid_height, RectHeight (&workarea));

			if (vid_width->integer < VID_MINIMUM_W) Cvar_SetValue (vid_width, VID_MINIMUM_W);
			if (vid_height->integer < VID_MINIMUM_H) Cvar_SetValue (vid_height, VID_MINIMUM_H);
		}

		// just flag that we need a mode change here; we'll pick it up and apply it at the start of the next frame
		vid.QueueModeChange = TRUE;
		recursive = FALSE;
	}
}


void VID_OnCreateDevice (void)
{
	// run our init handlers
	VID_RunHandlers (handler_init);

	// free all memory used for loading
	qScratch->FreeToLowMark (0);
}


void VID_OnReleaseDevice (void)
{
	// run our shutdown handlers
	VID_RunHandlers (handler_shutdown);
}


void VID_HandleAltEnter (void)
{
	if (!vid_fullscreen->integer)
	{
		Cvar_SetValue (vid_mode, vid.LastFullscreenMode);
		Cvar_SetValue (vid_fullscreen, 1);
	}
	else
	{
		Cvar_SetValue (vid_mode, vid.LastWindowedMode);
		Cvar_SetValue (vid_fullscreen, 0);
	}

	// skip drawing the next frame
	vid.SkipThisFrame = TRUE;
}


void VID_SetPalette (unsigned char *palette)
{
	// < 255 because index 255 is alpha
	for (int i = 0; i < 255; i++, palette += 3)
	{
		d_8to24table_rgba[i] = (255 << 24) + (palette[2] << 16) + (palette[1] << 8) + (palette[0] << 0);

		if (i > 223)
			d_8to24table_luma[i] = (255 << 24) + (palette[2] << 16) + (palette[1] << 8) + (palette[0] << 0);
		else d_8to24table_luma[i] = 0;
	}

	// 255 is transparent
	d_8to24table_rgba[255] = 0;
	d_8to24table_luma[255] = 0;
}


void VID_CenterWindow (HWND hWndCenter)
{
	RECT windowrect;
	RECT workarea;

	GetWindowRect (hWndCenter, &windowrect);
	SystemParametersInfo (SPI_GETWORKAREA, 0, &workarea, 0);

	MoveWindow (
		hWndCenter,
		workarea.left + (RectWidth (&workarea) - RectWidth (&windowrect)) / 2,
		workarea.top + (RectHeight (&workarea) - RectHeight (&windowrect)) / 2,
		RectWidth (&windowrect),
		RectHeight (&windowrect),
		TRUE
	);

	Sys_SendKeyEvents (TRUE);
}


BOOL VID_CreateWindow (DXGI_MODE_DESC *mode, BOOL windowed)
{
	WNDCLASS wc;

	// Register the frame class
	wc.style         = CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc   = (WNDPROC) MainWndProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = GetModuleHandle (NULL);
	wc.hIcon         = LoadIcon (GetModuleHandle (NULL), MAKEINTRESOURCE (IDI_ICON2));
	wc.hCursor       = NULL;
	wc.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH);
	wc.lpszMenuName  = 0;
	wc.lpszClassName = "DirectQRedux";

	if (!RegisterClass (&wc))
	{
		Sys_Error ("Couldn't register window class");
		return FALSE;
	}

	// get a good mode
	mode = VID_VerifyMode (mode, windowed);

	// set up the initially created centered window rectangle
	RECT WindowRect = {0, 0, mode->Width, mode->Height};
	RECT WorkArea;

	SystemParametersInfo (SPI_GETWORKAREA, 0, &WorkArea, 0);
	AdjustWindowRect (&WindowRect, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_VISIBLE, FALSE);

	int width = RectWidth (&WindowRect);
	int height = RectHeight (&WindowRect);

	// and create the window
	vid.Window = CreateWindow (
		"DirectQRedux",
		"DirectQRedux",
		WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_VISIBLE,
		WorkArea.left + (RectWidth (&WorkArea) - width) / 2,
		WorkArea.top + (RectHeight (&WorkArea) - height) / 2,
		width,
		height,
		NULL,
		NULL,
		GetModuleHandle (NULL),
		NULL
	);

	if (!vid.Window)
	{
		Sys_Error ("Couldn't create DIB window");
		return FALSE;
	}

	HDC hdc = GetDC (vid.Window);
	PatBlt (hdc, 0, 0, mode->Width, mode->Height, BLACKNESS);
	ReleaseDC (vid.Window, hdc);

	Con_Printf (PRINT_SAFE, "Initialized video mode: ");//\n", mode->Width, mode->Height);
	VID_DescribeCurrentMode_f ();

	return TRUE;
}


void VID_Shutdown (void)
{
	if (vid.Initialized)
	{
		vid.Initialized = FALSE;
		Sys_AppActivate (FALSE, FALSE);
		VID_ShutdownD3D ();
	}
}


int VID_FindMatchingMode (int width, int height)
{
	for (int i = 0; i < vid.NumModes; i++)
	{
		// the first mode that exceeds the requested width and height is the one to use
		if (vid.ModeList[i].Width < width) continue;
		if (vid.ModeList[i].Height < height) continue;

		return i;
	}

	// return the last mode which should be the desktop mode
	return (vid.NumModes - 1);
}


void VID_Init (void)
{
	InitCommonControls ();

	vid_mode = Cvar_Get ("vid_mode", "0", CVAR_ARCHIVE, VID_ModeChanged);
	vid_fullscreen = Cvar_Get ("vid_fullscreen", "0", CVAR_ARCHIVE, VID_ModeChanged);
	vid_width = Cvar_Get ("vid_width", "0", CVAR_ARCHIVE, VID_ModeChanged);
	vid_height = Cvar_Get ("vid_height", "0", CVAR_ARCHIVE, VID_ModeChanged);
	vid_vsync = Cvar_Get ("vid_vsync", "0", CVAR_ARCHIVE, Host_RearmTimers);	// vsync is no longer a mode change

	// init everything we need
	VID_EnumerateModes ();
	VID_InitCmds ();

	// force the vid_mode to update correctly (this will also sanity-check our cvar ranges)
	VID_ModeChanged ();

	// check our startup params
	int width = COM_CheckParm ("-width") ? Q_atoi (com_argv[COM_CheckParm ("-width") + 1]) : 0;
	int height = COM_CheckParm ("-height") ? Q_atoi (com_argv[COM_CheckParm ("-height") + 1]) : 0;

	if (width > 0 || height > 0) Cvar_SetValue (vid_mode, VID_FindMatchingMode (width, height));

	if (COM_CheckParm ("-window"))
	{
		Cvar_SetValue (vid_fullscreen, 0);

		// update windowed mode cvars too
		if (width > 0) Cvar_SetValue (vid_width, width);
		if (height > 0) Cvar_SetValue (vid_height, width);
	}

	// create our window in the desired mode
	if (!VID_CreateWindow (&vid.ModeList[vid_mode->integer], !vid_fullscreen->boolean))
	{
		Sys_Error ("VID_Init : failed to create main window");
		return;
	}

	// and now initialize D3D on it, starting up in mode 0
	D3D_CreateDevice (vid.Window, &vid.ModeList[vid_mode->integer], !vid_fullscreen->boolean);

	// update the last modes as appropriate
	if (vid_fullscreen->boolean)
		vid.LastFullscreenMode = vid_mode->integer;
	else vid.LastWindowedMode = vid_mode->integer;

	// init any one-time-only objects
	VID_OnCreateDevice ();

	// the menu is initialized after the device is created so that it can read caps
	VID_InitMenu ();

	// now we switch to fullscreen mode if desired because we created the swapchain as windowed
	// 2 reasons for this: first, the documentation recommends it; second: it just seems safer in
	// case any errors happen during startup; at least this way we'll get a breakpoint in MSVC
	if (vid_fullscreen->boolean)
	{
		ShowWindow (vid.Window, SW_SHOWNORMAL);
		d3d_SwapChain->SetFullscreenState (TRUE, NULL);
	}

	// video is up now
	vid.Initialized = TRUE;

	// this is a first Con_Printf to trigger a screen update as soon as video startup completes
	SCR_UpdateScreen (0);
	Con_Printf (PRINT_DEFAULT, "\n");
}


